/**
 * @file ringflow.c
 *
 * @brief Ring buffers
 *
 * Ringbuffer implementation store/access arbitrary binary data
 *
 * author : zhangerhu
 *
 */
#include "ringflow.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <stdbool.h>

static bool power_of_two(int x)
{
    return (x != 0) && ((x & (x - 1)) == 0);
}

static int roundup_pow_of_two(int x)
{
    int b = 0;
    for (int i = 0; i < 32; i++)
    {
        b = 1UL << i;
        if (x <= b)
        {
            break;
        }
    }
    return b;
}

rflow_t *rbuf_create(char *buf, int block_size, int block_count, int mode, int role)
{
    rflow_t *new_rb = NULL;
    if (buf == NULL)
    {
        new_rb = (rflow_t *)calloc(1, sizeof(rflow_t));
        if (!new_rb)
        {
            return NULL;
        }
        if (!power_of_two(block_count))
        {
            block_count = roundup_pow_of_two(block_count);
        }
        new_rb->block_count = block_count;
        new_rb->block_size = block_size;
        new_rb->size = block_count * block_size;
        new_rb->buf = (char *)malloc(block_size * block_count);
        if (!new_rb->buf)
        {
            free(new_rb);
            return NULL;
        }
        new_rb->run_mode = RBUF_MODE_SIMPLE;
        memset(new_rb->buf, 0, block_size * block_count);
    }
    else
    {
        new_rb = (rflow_t *)buf;
        new_rb->run_mode = RBUF_MODE_SHARE;
        new_rb->buf = (char *)buf + sizeof(rflow_t);
        if (role == 0)
        {
            new_rb->buf_s = (char *)buf + sizeof(rflow_t);
            memset(new_rb->buf, 0, block_size * block_count);
            new_rb->rfx = 0;
            new_rb->wfx = 0;
        }
        else
        {
            new_rb->buf_c = (char *)buf + sizeof(rflow_t);
        }
        new_rb->block_count = block_count;
        new_rb->block_size = block_size;
        new_rb->size = block_count * block_size;
        new_rb->mode = RBUF_MODE_BUF_OUT;
    }

    return new_rb;
}

int rbuf_write(rflow_t *rbuf, char *data, int len)
{
    char *buf;
    int write_pos, write_len;
    if (!rbuf || !data)
        return -1;
    if (len + RBUF_ALIGN > rbuf->block_size)
        len = rbuf->block_size - RBUF_ALIGN;

    // copy and make buffer readable
    if (rbuf->run_mode == RBUF_MODE_SIMPLE)
    {
        buf = rbuf->buf;
    }
    else
    {
        buf = rbuf->buf_s;
    }

    write_pos = rbuf->block_size * rbuf->wfx;
    write_len = BUF_USED | len;
    memcpy(buf + write_pos, &write_len, RBUF_ALIGN);
    memcpy(buf + write_pos + RBUF_ALIGN, data, len);
    rbuf->wfx = (rbuf->wfx + 1) % rbuf->block_count;

    return len;
}

int rbuf_read(rflow_t *rbuf, char *out, int len)
{
    char *buf;
    int tmp_len, read_len, read_pos;
    if (!rbuf || !buf)
        return -1;

    if (rbuf->run_mode == RBUF_MODE_SIMPLE)
    {
        buf = rbuf->buf;
    }
    else
    {
        buf = rbuf->buf_c;
    }

    // gets valid data length
    read_pos = rbuf->block_size * rbuf->rfx;
    memcpy(&tmp_len, buf + read_pos, RBUF_ALIGN);
    if (!(tmp_len & BUF_USED) || !tmp_len)
        return 0;

    read_len = tmp_len & ~BUF_USED;
    if (read_len > len)
        read_len = len;

    memcpy(out, buf + read_pos + RBUF_ALIGN, read_len);
    memset(buf + read_pos, 0, RBUF_ALIGN);
    rbuf->rfx = (rbuf->rfx + 1) % rbuf->block_count;

    return read_len;
}

int rbuf_read_post(rflow_t *rbuf, char *out, int len, int *post)
{
    char *buf;
    int tmp_post, tmp_len, read_len, read_pos;
    if (!rbuf || !out || !post)
        return -1;

    tmp_post = *post;
    // if post is -1, assign it to rbuf->rfx
    if (tmp_post == -1 || tmp_post < 0 || tmp_post >= rbuf->block_count)
        tmp_post = rbuf->rfx;

    // if post is equal to rbuf->wfx, it is equivalent to no data
    // if read too slowly, it may result in data loss
    if (tmp_post == rbuf->wfx)
        return 0;

    if (rbuf->run_mode == RBUF_MODE_SIMPLE)
    {
        buf = rbuf->buf;
    }
    else
    {
        buf = rbuf->buf_c;
    }

    read_pos = rbuf->block_size * tmp_post;
    memcpy(&tmp_len, buf + read_pos, RBUF_ALIGN);
    if (!(tmp_len & BUF_USED) || !tmp_len)
        return 0;

    read_len = tmp_len & ~BUF_USED;
    if (read_len > len)
        read_len = len;

    memcpy(out, buf + read_pos + RBUF_ALIGN, read_len);
    tmp_post = (tmp_post + 1) % rbuf->block_count;
    *post = tmp_post;

    return read_len;
}

void rbuf_destroy(rflow_t *rbuf)
{
    if (!rbuf)
        return;
    if (rbuf->mode != RBUF_MODE_BUF_OUT && rbuf->buf != NULL)
    {
        free(rbuf->buf);
        free(rbuf);
    }
}